#include "MainWindow.h"

#include "DocumentWidget.h"
#include "TaskWidget.h"
#include "Gui.h"
#include "LandPatternEditor/LandPatternWidget.h"
#include "LandPatternEditor/PlaceArrangementTask.h"
#include "LandPatternEditor/RenumberTerminalsTask.h"
#include "PadStackEditor/PadStackWidget.h"

#include "LeIpc7351/LandPattern.h"
#include "LeIpc7351/PadStack.h"
#include "LeIpc7351/PadStackElement.h"
#include "LeIpc7351/DocumentObjectFactory.h"
#include "LeIpc7351/Primitives/CirclePrimitive.h"
#include "LeIpc7351/Primitives/RectanglePrimitive.h"

#include "LeDocumentObject/Document.h"
#include "LeDocumentObject/IDocumentObject.h"
#include "LeDocumentObject/DocumentObjectInspector.h"

#include <QAction>
#include <QDebug>
#include <QDockWidget>
#include <QFileInfo>
#include <QHeaderView>
#include <QLayout>
#include <QMenu>
#include <QMenuBar>
#include <QSettings>
#include <QStackedWidget>
#include <QTableView>
#include <QTimer>
#include <QToolBar>

MainWindow::MainWindow(QWidget *parent)
    : QMainWindow(parent)
{
    registerMetaTypes();

    createActions();
    createMenus();
    createToolBars();
    createDockWidgets();
    createCentralWidget();

    populateMenus();
    connectActions();
    updateDocumentActions();

    auto timer = new QTimer(this);
    timer->setSingleShot(true);
    connect(timer, &QTimer::timeout,
            this, &MainWindow::restoreGui);
    timer->start(0);
}

MainWindow::~MainWindow()
{

}

void MainWindow::openEditor(LDO::IDocumentObject *object)
{
    if (object == nullptr)
    {
        qWarning() << "MainWindow::openEditor: Attempting to edit a null object";
        return;
    }

    EditorWidget *editor;

    if (!m_editorForObject.contains(object))
    {
        switch (object->objectType())
        {
            case PadStack::Type:
                editor = new PadStackWidget;
                break;
            case LandPattern::Type:
                editor = new LandPatternWidget;
                break;
            default:
                qWarning() << "MainWindow::openEditor: Couldn't find suitable editor for object"
                           << object->objectName() << object->objectUserName() << object->objectType();
                return;
        }
        m_editorStackedWidget->addWidget(editor);
        m_editorForObject.insert(object, editor);
        editor->edit(this, m_document, object);
    }
    else
        editor = m_editorForObject.value(object);

    setCurrentEditor(editor);
}

void MainWindow::setCurrentEditor(EditorWidget *editor)
{
    desactivateEditor(currentEditor());
    m_editorStackedWidget->setCurrentWidget(editor);
    activateEditor(editor);
}

void MainWindow::activateEditor(EditorWidget *editor)
{
    setWindowTitle(editor->title());
    for (auto task: editor->tasks())
    {
        auto action = task->action();
        m_taskToolBar->addAction(action);
        menu("menu-place")->addAction(action);
    }
    connect(editor, &EditorWidget::taskStarted,
            this, &MainWindow::onTaskStarted);
    connect(editor, &EditorWidget::taskFinished,
            this, &MainWindow::onTaskFinished);
}

void MainWindow::desactivateEditor(EditorWidget *editor)
{
    setWindowTitle("None");
    for (auto task: editor->tasks())
    {
        auto action = task->action();
        m_taskToolBar->removeAction(action);
        menu("menu-place")->removeAction(action);
    }
    disconnect(editor, &EditorWidget::taskStarted,
               this, &MainWindow::onTaskStarted);
    disconnect(editor, &EditorWidget::taskFinished,
               this, &MainWindow::onTaskFinished);
}

QAction *MainWindow::action(const QString &id) const
{
    return m_actionMap.value(id, nullptr);
}

void MainWindow::enableAction(const QString &id, bool enable)
{
    if (m_actionMap.contains(id))
        m_actionMap.value(id)->setEnabled(enable);
}

void MainWindow::disableAction(const QString &id, bool disable)
{
    if (m_actionMap.contains(id))
        m_actionMap.value(id)->setDisabled(disable);
}

QMenu *MainWindow::menu(const QString &id) const
{
    return m_menuMap.value(id, nullptr);
}

LDO::DocumentObjectInspector *MainWindow::objectInspector() const
{
    return m_documentObjectInspector;
}

void MainWindow::createMenus()
{
    QMenu *menu;
    menu = menuBar()->addMenu("&File");
    m_menuMap.insert("menu-file", menu);
    menu = menuBar()->addMenu("&Edit");
    m_menuMap.insert("menu-edit", menu);
    menu = menuBar()->addMenu("&Place");
    m_menuMap.insert("menu-place", menu);
    menu = menuBar()->addMenu("&Object");
    m_menuMap.insert("menu-object", menu);
    menu = menuBar()->addMenu("&Tools");
    m_menuMap.insert("menu-tools", menu);
    menu = menuBar()->addMenu("&Window");
    m_menuMap.insert("menu-window", menu);
    menu = menuBar()->addMenu("&Help");
    m_menuMap.insert("menu-help", menu);
}

void MainWindow::createActions()
{
    QAction *action;
    action = new QAction(Gui::icon("document-new"), "&New...", this);
    action->setShortcut(QKeySequence::New);
    m_actionMap.insert("document-new", action);
    action = new QAction(Gui::icon("document-open"), "&Open...", this);
    action->setShortcut(QKeySequence::Open);
    m_actionMap.insert("document-open", action);
    action = new QAction(Gui::icon("document-save"), "&Save", this);
    action->setShortcut(QKeySequence::Save);
    m_actionMap.insert("document-save", action);
    action = new QAction(Gui::icon("document-save"), "&Save as...", this);
    action->setShortcut(QKeySequence::SaveAs);
    m_actionMap.insert("document-save-as", action);
    action = new QAction(Gui::icon("document-close"), "&Close", this);
    action->setShortcut(QKeySequence::Close);
    m_actionMap.insert("document-close", action);
    action = new QAction(Gui::icon("object"), "Edit object", this);
    m_actionMap.insert("edit-object", action);
    action = new QAction(Gui::icon("edit-cut"), "Cut", this);
    action->setShortcut(QKeySequence::Cut);
    m_actionMap.insert("edit-cut", action);
    action = new QAction(Gui::icon("edit-copy"), "Copy", this);
    action->setShortcut(QKeySequence::Copy);
    m_actionMap.insert("edit-copy", action);
    action = new QAction(Gui::icon("edit-paste"), "Paste", this);
    action->setShortcut(QKeySequence::Paste);
    m_actionMap.insert("edit-paste", action);
    action = new QAction(Gui::icon("edit-delete"), "Delete", this);
    action->setShortcut(QKeySequence::Delete);
    m_actionMap.insert("edit-delete", action);
}

void MainWindow::createToolBars()
{
    m_taskToolBar = addToolBar("Tasks");
}

void MainWindow::createDockWidgets()
{
    m_documentDockWidget = new QDockWidget("Document");
    m_documentDockWidget->setObjectName("documentDockWidget");
    m_documentWidget = new DocumentWidget();
    m_documentDockWidget->setWidget(m_documentWidget);
    addDockWidget(Qt::LeftDockWidgetArea, m_documentDockWidget);

    m_taskDockWidget = new QDockWidget("Task");
    m_taskDockWidget->setObjectName("taskDockWidget");
    m_taskWidget = new TaskWidget();
    m_taskDockWidget->setWidget(m_taskWidget);
    m_taskDockWidget->hide();
    addDockWidget(Qt::LeftDockWidgetArea, m_taskDockWidget);

    tabifyDockWidget(m_documentDockWidget, m_taskDockWidget);

    m_documentObjectInspectorDockWidget = new QDockWidget("Object inspector");
    m_documentObjectInspectorDockWidget->setObjectName("documentObjectInspectorDockWidget");
    m_documentObjectInspector= new LDO::DocumentObjectInspector();
    m_documentObjectInspectorDockWidget->setWidget(m_documentObjectInspector);
    addDockWidget(Qt::RightDockWidgetArea, m_documentObjectInspectorDockWidget);
}

void MainWindow::createCentralWidget()
{
    m_editorStackedWidget = new QStackedWidget();
    setCentralWidget(m_editorStackedWidget);
    layout()->setMargin(0);
    layout()->setSpacing(0);
}

void MainWindow::populateMenus()
{
    QMenu *editMenu = menu("menu-edit");
    editMenu->addAction(action("edit-delete"));
    editMenu->addSeparator();
    editMenu->addAction(action("edit-cut"));
    editMenu->addAction(action("edit-copy"));
    editMenu->addAction(action("edit-paste"));
    editMenu->addSeparator();
    QMenu *fileMenu = menu("menu-file");
    fileMenu->addAction(action("document-new"));
    fileMenu->addAction(action("document-open"));
    fileMenu->addAction(action("document-save"));
    fileMenu->addAction(action("document-save-as"));
    fileMenu->addAction(action("document-close"));
}

void MainWindow::connectActions()
{

    connect(action("document-new"), &QAction::triggered,
            this, &MainWindow::newDocumentRequested);
    connect(action("document-open"), &QAction::triggered,
            this, &MainWindow::openDocumentRequested);
    connect(action("document-save"), &QAction::triggered,
            this, &MainWindow::saveDocumentRequested);
    connect(action("document-save-as"), &QAction::triggered,
            this, &MainWindow::saveDocumentAsRequested);
    connect(action("document-close"), &QAction::triggered,
            this, &MainWindow::closeDocumentRequested);
}

void MainWindow::registerMetaTypes()
{
}

LDO::Document *MainWindow::currentDocument() const
{
    return m_document;
}

void MainWindow::updateDocumentActions()
{
    bool enabled = m_document != nullptr;
    enableAction("document-save", enabled);
    enableAction("document-save-as", enabled);
    enableAction("document-close", enabled);
}

void MainWindow::setCurrentDocument(LDO::Document *doc)
{
    // TODO: check for open editors, close them all

    auto oldDoc = m_document;

    m_document = doc;
    m_documentWidget->setDocument(this, m_document);

    if (m_document != nullptr)
        setWindowTitle(m_document->objectName());
    else
        setWindowTitle("");

    if (oldDoc != nullptr)
        delete oldDoc;

    updateDocumentActions();
}

EditorWidget *MainWindow::currentEditor() const
{
    if (m_editorStackedWidget->count() == 0)
        return nullptr;
    return qobject_cast<EditorWidget*>(m_editorStackedWidget->currentWidget());
}

LDO::IDocumentObject *MainWindow::currentObject() const
{
    auto editor = currentEditor();
    if (editor == nullptr)
        return nullptr;
    return editor->object();
}

//void MainWindow::setupToolBar()
//{
//    m_toolBar = addToolBar("Tasks tool bar");
//    m_toolBar->setObjectName("taskToolBar");
//    m_toolBar->addActions(m_taskActionGroup->actions());
//    m_toolBar->addSeparator();
//    m_toolBar->addAction(Gui::icon("arrangement-dual"), "Arrange dual");
//    m_toolBar->addAction(Gui::icon("arrangement-dual-in-line"), "Arrange dual in line");
//    m_toolBar->addAction(Gui::icon("arrangement-quad-in-line"), "Arrange quad in line");
//    m_toolBar->addAction(Gui::icon("arrangement-corner"), "Arrange in corners");
//    m_toolBar->addAction(Gui::icon("arrangement-triangle"), "Arrange in triangle");
//    m_toolBar->addAction(Gui::icon("arrangement-array"), "Arrange in array");
//    m_toolBar->addAction(Gui::icon("arrangement-center"), "Arrange center");
//    m_toolBar->addSeparator();
//    m_toolBar->addAction(Gui::icon("body-molded-flat"), "Molded flat");
//    m_toolBar->addAction(Gui::icon("body-molded-bottom"), "Molded bottom");
//    m_toolBar->addAction(Gui::icon("body-molded-center"), "Molded center");
//    m_toolBar->addAction(Gui::icon("body-molded-top"), "molded top");
//    m_toolBar->addAction(Gui::icon("body-ceramic-flat"), "Ceramic flat");
//    m_toolBar->addAction(Gui::icon("body-ceramic-chip"), "Ceramic chip");
//    m_toolBar->addAction(Gui::icon("body-ceramic-substrate"), "Ceramic substrate");
//    m_toolBar->addAction(Gui::icon("body-pcb-substrate"), "PCB substrate");
//    m_toolBar->addSeparator();
//    m_toolBar->addAction(Gui::icon("terminal-gull-lead"), "Gull lead");
//    m_toolBar->addAction(Gui::icon("terminal-in-l-lead"), "In L lead");
//    m_toolBar->addAction(Gui::icon("terminal-out-l-lead"), "Out L lead");
//    m_toolBar->addAction(Gui::icon("terminal-under-l-lead"), "Under-out L lead");
//    m_toolBar->addAction(Gui::icon("terminal-j-lead"), "J lead");
//    m_toolBar->addSeparator();
//    m_toolBar->addAction(Gui::icon("padstack-role-surface-terminal"), "Surface terminal");
//    m_toolBar->addAction(Gui::icon("padstack-role-through-terminal"), "Through terminal");
//    m_toolBar->addAction(Gui::icon("padstack-role-fiducial"), "Fiducial");
//    m_toolBar->addAction(Gui::icon("padstack-role-via2"), "Via");
//    m_toolBar->addAction(Gui::icon("padstack-role-mechanical2"), "Mechanical");
//    m_toolBar->addAction(Gui::icon("padstack-role-testpoint"), "Test point");
//}

void MainWindow::onTaskStarted(AbstractTask *task)
{
    Q_UNUSED(task)
    m_taskDockWidget->show();
    if (!tabifiedDockWidgets(m_taskDockWidget).isEmpty())
        tabifyDockWidget(tabifiedDockWidgets(m_taskDockWidget).first(), m_taskDockWidget);
    m_taskWidget->setTask(task);
    m_taskWidget->setFocus();
}

void MainWindow::onTaskFinished(AbstractTask *task)
{
    task->disconnect(this);
    m_taskDockWidget->hide();
}

#include <QDebug>
#include <QKeyEvent>
#include <QFocusEvent>
#define DEBUG() qDebug() << __PRETTY_FUNCTION__

void MainWindow::keyPressEvent(QKeyEvent *event)
{
    DEBUG() << event->key();
    QWidget::keyPressEvent(event);
}

void MainWindow::keyReleaseEvent(QKeyEvent *event)
{
    DEBUG() << event->key();
    QWidget::keyReleaseEvent(event);
}

void MainWindow::focusInEvent(QFocusEvent *event)
{
    DEBUG();
    QWidget::focusInEvent(event);
}

void MainWindow::focusOutEvent(QFocusEvent *event)
{
    DEBUG();
    QWidget::focusOutEvent(event);
}

void MainWindow::enterEvent(QEvent *event)
{
    DEBUG();
    QWidget::enterEvent(event);
}

void MainWindow::leaveEvent(QEvent *event)
{
    DEBUG();
    QWidget::leaveEvent(event);
}

void MainWindow::restoreGui()
{
    resize(1024, 780);
    restoreGeometryAndState();
}

void MainWindow::restoreGeometryAndState()
{
    QSettings settings;
    if (settings.childGroups().contains(objectName()))
    {
        settings.beginGroup(objectName());
        restoreGeometry(settings.value("geometry").toByteArray());
        restoreState(settings.value("state").toByteArray());
        settings.endGroup();
    }
    //    if (settings.childGroups().contains(m_editorWidget->objectName()))
    //    {
    //        settings.beginGroup(m_editorWidget->objectName());
    //        m_editorWidget->restoreGeometryAndState(settings);
    //        settings.endGroup();
    //    }
}

void MainWindow::saveGeometryAndState()
{
    QSettings settings;
    settings.beginGroup(objectName());
    settings.setValue("geometry", saveGeometry());
    settings.setValue("state", saveState());
    settings.endGroup();
    //    settings.beginGroup(m_editorWidget->objectName());
    //    m_editorWidget->saveGeometryAndState(settings);
    //    settings.endGroup();
}


#include <QApplication>
#include <QFileDialog>
#include <QMessageBox>

#include "LeDocumentObject/DocumentStreamReader.h"
#include "LeDocumentObject/DocumentStreamWriter.h"

#include "LeIpc7351/DocumentObjectFactory.h"

void MainWindow::openDocumentRequested()
{
    // TODO: First close (and save) current document
    // unless we allow multiple documents

    QString fileName = QFileDialog::getOpenFileName(this, "Open", QDir::currentPath(),
                                                    "*.xml");
    if (fileName.isNull())
        return;

    QDir dir;
    fileName = dir.absoluteFilePath(fileName);

    QFile file(fileName);
    if (!file.open(QFile::ReadWrite))
    {
        QMessageBox::critical(this, "Cannot open document",
                              QString("An error occured while opening the document for reading:\n"
                                      "%1").arg(file.errorString()));
        return;
    }

    LDO::DocumentStreamReader reader;
    reader.setDevice(&file);
    reader.setStrictMode(true);
    LDO::Document *doc = new LDO::Document(this);
    doc->registerFactory(new DocumentObjectFactory(this));

    if (!reader.readDocument(doc))
    {
        delete doc;
        QMessageBox::critical(this, "Cannot read document",
                              QString("An error occured while reading the document:\n"
                                      "%1").arg(reader.errorString()));
        return;
    }
    doc->setFileName(fileName);

    setCurrentDocument(doc);
}

// TODO: Add a '*' in window title to reflect 'need save'
void MainWindow::saveDocumentRequested()
{
    if (currentDocument() == nullptr)
        return;

    if (currentDocument()->fileName().isEmpty())
        saveDocumentAsRequested();

    QFile file(currentDocument()->fileName());
    if (!file.open(QFile::ReadWrite))
    {
        QMessageBox::critical(this, "Cannot open document",
                              QString("An error occured while opening the document for writing:\n"
                                      "%1").arg(file.errorString()));
        return;
    }

    LDO::DocumentStreamWriter writer;
    writer.setDevice(&file);
    if (!writer.writeDocument(currentDocument()))
    {
        QMessageBox::critical(this, "Cannot write document",
                              QString("An error occured while writing the document:\n"
                                      "%1").arg(writer.errorString()));
        return;
    }
}

void MainWindow::saveDocumentAsRequested()
{
    if (currentDocument() == nullptr)
        return;

    QDir dir;
    const QString filePath = dir.absoluteFilePath(currentDocument()->objectName());

    QString fileName = QFileDialog::getSaveFileName(this, "Open", filePath, "*.xml");
    if (fileName.isNull())
        return;

    if (!fileName.endsWith(".xml"))
        fileName.append(".xml");

    QFile file(currentDocument()->fileName());
    if (!file.open(QFile::ReadWrite))
    {
        QMessageBox::critical(this, "Cannot open document",
                              QString("An error occured while opening the document for writing:\n"
                                      "%1").arg(file.errorString()));
        return;
    }

    LDO::DocumentStreamWriter writer;
    writer.setDevice(&file);
    if (!writer.writeDocument(currentDocument()))
    {
        QMessageBox::critical(this, "Cannot write document",
                              QString("An error occured while writing the document:\n"
                                      "%1").arg(writer.errorString()));
        return;
    }

    currentDocument()->setFileName(fileName);
}

void MainWindow::closeDocumentRequested()
{
    if (currentDocument() == nullptr)
        return;

    // TODO: Check if document needs saving
    setCurrentDocument(nullptr);
}

void MainWindow::newDocumentRequested()
{
    static int count = 0;
    auto doc = new LDO::Document(this);
    doc->setObjectName(QString("Untitled%1").arg(count++));
    auto factory = new DocumentObjectFactory(this);
    doc->registerFactory(factory);

    setCurrentDocument(doc);
}


void MainWindow::closeEvent(QCloseEvent *event)
{
    saveGeometryAndState();
    event->accept();
}
